/**
 *  Simple User-Level Threading Library Demonstration Code
 *
 *  This code is intended as a demonstration of a few of the more low-level
 *  issues involved in using setjmp/longjmp as the basis for a user-level
 *  thread library.  A jmp_buf struct is used to hold the thread context.
 *
 *  This code is very far from what the assignment requires.  You should think
 *  of it not even as a starting point, just as a reference.
 *
 *  Code tested on Ubuntu 10.4, gcc 4.4.5, -O0 optimization level... -O1 or
 *  higher will lead to run-time failures, for known reasons outside the scope
 *  of this project
 */

#include <unistd.h>
#include <cstdlib>
#include <setjmp.h>
#include <cstdio>
#include <signal.h>

/**
 *  Defines for interacting with the thread context struct
 *
 *  NB: _BP would be 3
 */
#define _SP 4
#define _PC 5

/**
 *  Linux hides addresses in jump buffers by xoring them with gs:0x18 and then
 *  rotating them left 9.  This code does the same, so that we can put our own
 *  addresses into a jump buffer
 *
 *  NB: gcc inline assembly "=r" indicates an in/out parameter
 */
int xor_and_rol(void* p)
{
    int ret = (int)p;
#ifndef __sun__
#warning "Linux"
    asm volatile ("xorl %%gs:0x18, %0;\n roll $0x9, %0;" : "=r"(ret));
#endif
    return ret;
}

/**
 *  This is just enough to get our threads working with a simple round-robin
 *  scheduler
 */
const unsigned THREADS     = 4;         // total number of threads
volatile int   curr_thread = 0;         // the current thread
char           stack[THREADS * 8192];   // stack for each thread
jmp_buf        context[THREADS];        // jump buffer for each thread

/**
 *  This is a pretty weak way to switch among threads
 */
void yield_me()
{
    // wait briefly, so that we can read the output of our threads
    usleep(1000000);

    // on a call to yield_me, this call to setjmp will result in a return value
    // of 0.  A zero return value means that we will follow on to the
    // 'curr_thread = ...' line, where we will change the current thread and
    // longjmp to that thread.  That will, in turn, put us back on this next
    // setjmp line, but with a different context/stack and a return value of
    // 403, which will lead to us returning to a different thread than called
    // this function
    if (setjmp(context[curr_thread]) == 403)
        return;

    // switch to the next thread (round robin)
    curr_thread = (curr_thread + 1) % THREADS;
    longjmp(context[curr_thread], 403);
}

/**
 *  Declare a few functions for our threads to run... note that our current
 *  setup takes functions of the type:
 *    void (*)(void)
 */
void d() { int i = 0;  while (1) { printf("in d: %d\n", i++); yield_me(); } }
void e() { int i = 33; while (1) { printf("in e: %d\n", i++); yield_me(); } }
void f() { int i = 42; while (1) { printf("in f: %d\n", i++); yield_me(); } }
void g() { int i = 9;  while (1) { printf("in g: %d\n", i++); yield_me(); } }

/**
 *  This is just enough to demonstrate how our threads work
 */
int main()
{
    // for each thread, create a default context with the right stack
    for (unsigned i = 0; i < THREADS; ++i) {
        setjmp(context[i]);
        ((unsigned*)context[i])[_SP] = xor_and_rol(&stack[(i + 1) * 8192]);
    }

    // set each context's PC
    ((unsigned*)context[0])[_PC] = xor_and_rol((void*)d);
    ((unsigned*)context[1])[_PC] = xor_and_rol((void*)e);
    ((unsigned*)context[2])[_PC] = xor_and_rol((void*)f);
    ((unsigned*)context[3])[_PC] = xor_and_rol((void*)g);

    // switch to the first thread... note that this program will never return
    longjmp(context[0], 1);
}

